﻿// DigitalDesign
// Project: InformationBoard
// Developer: Montak Oleg
// Created: 13.11.2011
// Last change: 17.11.2011
// Description: implementation of ScheduleService - provides all transactions with the schedule

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Data;
using System.Data.Objects;

namespace Server
{
    public class ScheduleService : IScheduleService
    {
        private DataModelContainer dataModel;
        public ScheduleService()
        {
            dataModel = Context.Instance;
        }

        #region get-methods

        // осторожно! говнокод!
        public ActualScheduleResult GetActualSchedule(int clientId, IEnumerable<DateTime> dates)
        {
            var resultData = new ActualScheduleResult();
            resultData.IsGood = true;

            try
            {
                // весь транспорт в расписании привязан к location, а не к конкретному клиенту
                // получаем location клиента
                var location = (from client in dataModel.ClientDevices
                                where client.Id == clientId
                                select client.Location).First();

                foreach (var date in dates)
                {
                    // в БД дни недели начинаются с 1, первый день - воскресенье
                    int dayOfWeek = (int)date.DayOfWeek + 1;
                    var day = dataModel.DaysOfWeeks.Single(d => d.Id == dayOfWeek);


                    foreach (var scheduleNode in location.Schedules)
                    {
                        // выберем из них все, соотв. текущему дню недели
                        if (scheduleNode.DaysOfWeek.Contains(day))
                        {
                            // и для каждой записи проверим есть ли оперативные изменения
                            var changeNode = scheduleNode.ScheduleChanx.SingleOrDefault();

                            // если изменения есть - достаем информацию из них, если нет - из основного расписания
                            wActualScheduleNode newItem;
                            if (changeNode != null && changeNode.DateOfChange == date.Date)
                            {
                                if (changeNode.Canceled)
                                    continue;
                                newItem = new wActualScheduleNode(changeNode, date);
                            }
                            else
                            {
                                newItem = new wActualScheduleNode(scheduleNode, date);
                            }
                            newItem.Id = scheduleNode.Id;
                            resultData.Data.Add(newItem);
                        }
                    }

                    // не забудем также достать Добавленные поезда
                    // они не привязаны к основному расписанию: ScheduleId = null
                    foreach (var changeNode in location.ScheduleChanges)
                    {
                        if (changeNode.DateOfChange == date.Date && changeNode.ScheduleId == null && !changeNode.Canceled)
                        {
                            var newItem = new wActualScheduleNode(changeNode, date);
                            resultData.Data.Add(newItem);
                        }
                    }
                }

                resultData.DataVersion = DataVersionManager.Instance.GetDataVersionEx(clientId);
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }


        public ScheduleChangesResult GetChangesSchedule(int stationId, DateTime? dateStart = null, DateTime? dateEnd = null)
        {
            var resultData = new ScheduleChangesResult();
            resultData.IsGood = true;

            if (dateStart == null)
                dateStart = Helper.GetMinDate();
            if (dateEnd == null)
                dateEnd = Helper.GetMaxDate();

            var changeQuery = from change in dataModel.ScheduleChanges
                              where change.Location.StationId == stationId &&
                                    change.DateOfChange >= dateStart &&
                                    change.DateOfChange <= dateEnd
                              select change;
            try
            {
                foreach (var change in changeQuery)
                {
                    var changeWrapper = new wScheduleChanxNode(change);
                    resultData.Data.Add(changeWrapper);
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        public ScheduleChangesResult GetCancelledSchedule(int stationId, DateTime? dateStart = null, DateTime? dateEnd = null)
        {
            var resultData = new ScheduleChangesResult();
            resultData.IsGood = true;

            if (dateStart == null)
                dateStart = Helper.GetMinDate();
            if (dateEnd == null)
                dateEnd = Helper.GetMaxDate();

            var changeQuery = from change in dataModel.ScheduleChanges
                              where change.Location.StationId == stationId &&
                                    change.Canceled &&
                                    change.DateOfChange >= dateStart &&
                                    change.DateOfChange <= dateEnd
                              select change;

            try
            {
                foreach (var change in changeQuery)
                {
                    var changeWrapper = new wScheduleChanxNode(change);
                    resultData.Data.Add(changeWrapper);
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        public ScheduleChangesResult GetAddedSchedule(int stationId, DateTime? dateStart = null, DateTime? dateEnd = null)
        {
            var resultData = new ScheduleChangesResult();
            resultData.IsGood = true;

            if (dateStart == null)
                dateStart = Helper.GetMinDate();
            if (dateEnd == null)
                dateEnd = Helper.GetMaxDate();

            var changeQuery = from change in dataModel.ScheduleChanges
                              where change.Location.StationId == stationId &&
                                    change.ScheduleId == null &&
                                    change.DateOfChange >= dateStart &&
                                    change.DateOfChange <= dateEnd
                              select change;

            try
            {
                foreach (var changeNode in changeQuery)
                {
                    var changeWrapper = new wScheduleChanxNode(changeNode);
                    resultData.Data.Add(changeWrapper);
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        public ScheduleResult GetSchedule(int stationId)
        {
            var resultData = new ScheduleResult();
            resultData.IsGood = true;

            var scheduleQuery = from schedule in dataModel.Schedules
                                where schedule.Location.StationId == stationId
                                select schedule;
            try
            {
                foreach (var scheduleNode in scheduleQuery)
                {
                    var scheduleWrapper = new wScheduleNode(scheduleNode);
                    resultData.Data.Add(scheduleWrapper);
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        public LocationsResult GetLocations(int stationId)
        {
            var resultData = new LocationsResult();
            resultData.IsGood = true;

            var locationsQuery = from loc in dataModel.Locations
                                 where loc.StationId == stationId
                                 select loc;
            try
            {
                foreach (var loc in locationsQuery)
                {
                    resultData.Data.Add(new wLocation(loc));
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        public DaysOfWeekResult GetDaysOfWeek()
        {
            var resultData = new DaysOfWeekResult();
            resultData.IsGood = true;

            try
            {
                foreach (var day in dataModel.DaysOfWeeks)
                {
                    resultData.Data.Add(new wDayOfWeek(day));
                }
            }
            catch (Exception ex)
            {
                resultData.IsGood = false;
                Log.Add(this, ex);
            }

            return resultData;
        }

        #endregion

        #region set-methods
        public bool SetCancelledSchedule(int scheduleId, DateTime date, bool canceled)
        {
            try
            {
                // перво-наперво найдем запись в расписании, которую хотим отменить/включить
                var scheduleNode = dataModel.Schedules.Single(s => s.Id == scheduleId);

                // теперича надо посмотреть: "а есть ли что-то о ней в табличке опер. изменений"
                //var changeNode = scheduleNode.ScheduleChanx.Where(t => t.DateOfChange == date.Date).FirstOrDefault();
                var changeNode = scheduleNode.ScheduleChanx.SingleOrDefault(t => t.DateOfChange == date.Date);
                if (changeNode == null)
                {
                    // если записи в опер. изменениях нет, её надо создать
                    // создавать будем путём копирования всех полей из ScheduleNode
                    changeNode = new ScheduleChanx();
                    changeNode.Update(scheduleNode);
                    dataModel.ScheduleChanges.AddObject(changeNode);
                }
                if (changeNode.Canceled != canceled)
                {
                    changeNode.Canceled = canceled;
                    changeNode.DateOfChange = date.Date;
                    dataModel.SaveChanges();
                    DataVersionManager.Instance.IncSchedulesVersion(changeNode);
                }
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }

            return true;
        }

        public bool AddScheduleChange(wScheduleChanxNode wChange)
        {
            try
            {
                //checkUniqueScheduleChanges(wChange);
                // если изменение на этот день, для этого поезда уже есть в БД - заменяем их
                var dublicate = getDublicateOfScheduleChanx(wChange);
                if (dublicate != null)
                    return ReplaceScheduleChanges(dublicate.Id, wChange);

                var newChange = new ScheduleChanx();
                newChange.Update(wChange);
                dataModel.ScheduleChanges.AddObject(newChange);
                try
                {
                    dataModel.SaveChanges();
                }
                catch (UpdateException)
                {
                    dataModel.Detach(newChange);
                    throw;
                }
                DataVersionManager.Instance.IncSchedulesVersion(newChange);
                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool DeleteScheduleChanges(int scheduleChangeId)
        {
            try
            {
                var changes = (from change in dataModel.ScheduleChanges
                               where change.Id == scheduleChangeId
                               select change).First();
                DataVersionManager.Instance.IncSchedulesVersion(changes);

                dataModel.DeleteObject(changes);
                dataModel.SaveChanges();

                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool DeleteScheduleChanges(DateTime? dateStart = null, DateTime? dateEnd = null)
        {
            if (dateStart == null)
                dateStart = Helper.GetMinDate();
            if (dateEnd == null)
                dateEnd = Helper.GetMaxDate();

            var changesQuery = from change in dataModel.ScheduleChanges
                               where change.DateOfChange >= dateStart &&
                                     change.DateOfChange <= dateEnd
                               select change;
            try
            {
                foreach (var change in changesQuery)
                {
                    DataVersionManager.Instance.IncSchedulesVersion(change);
                    dataModel.DeleteObject(change);
                }
                dataModel.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool ReplaceScheduleChanges(int scheduleChangeId, wScheduleChanxNode newChanges)
        {
            try
            {
                newChanges.Id = scheduleChangeId;
                checkUniqueScheduleChanges(newChanges);

                var oldChanges = (from c in dataModel.ScheduleChanges
                                  where c.Id == scheduleChangeId
                                  select c).First();

                // увеличиваем версию данных для клиентов, которые будут затронуты
                int oldLocationId = oldChanges.LocationId;
                DataVersionManager.Instance.IncSchedulesVersion(oldChanges);

                oldChanges.Update(newChanges);
                try
                {
                    dataModel.SaveChanges();
                }
                catch (UpdateException)
                {
                    dataModel.Refresh(RefreshMode.StoreWins, oldChanges);
                    throw;
                }

                // если в расписании изменилась платформа(Location), у него могут появиться новые клиенты, 
                // которых также надо уведомить
                if (oldChanges.LocationId != oldLocationId)
                    DataVersionManager.Instance.IncSchedulesVersion(oldChanges);

                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool AddSchedule(wScheduleNode schedule)
        {
            var newSchedule = new Schedule();
            try
            {
                checkNewSchedule(schedule);

                newSchedule.Update(schedule);
                dataModel.Schedules.AddObject(newSchedule);
                try
                {
                    dataModel.SaveChanges();
                }
                catch (UpdateException)
                {
                    dataModel.Schedules.Detach(newSchedule);
                    throw;
                }
                DataVersionManager.Instance.IncSchedulesVersion(newSchedule);
                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool DeleteSchedule(int scheduleId)
        {
            try
            {
                var schedule = dataModel.Schedules.Include("daysOfWeek").Single(s => s.Id == scheduleId);
                //var schedule = dataModel.Schedules.Single(s => s.Id == scheduleId);
                DataVersionManager.Instance.IncSchedulesVersion(schedule);

                // вот такой велосипел каскадного удаления
                // upd: сделали таки в БД
                //while (schedule.ScheduleChanx.Count > 0)
                //{
                //    dataModel.DeleteObject(schedule.ScheduleChanx.First());
                //}

                dataModel.DeleteObject(schedule);
                dataModel.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        public bool ReplaceSchedule(int scheduleId, wScheduleNode newSchedule)
        {
            try
            {
                checkNewSchedule(newSchedule);

                var schedule = dataModel.Schedules.Single(s => s.Id == scheduleId);
                int oldLocation = schedule.LocationId;
                DataVersionManager.Instance.IncSchedulesVersion(schedule);
                schedule.Update(newSchedule);
                try
                {
                    dataModel.SaveChanges();
                }
                catch (UpdateException)
                {
                    dataModel.Refresh(RefreshMode.StoreWins, schedule);
                    throw;
                }

                if (schedule.LocationId != oldLocation)
                    DataVersionManager.Instance.IncSchedulesVersion(schedule);

                return true;
            }
            catch (Exception ex)
            {
                Log.Add(this, ex);
                return false;
            }
        }

        #endregion

        #region Вспомогательные штуки

        /// <summary>
        /// Проверяет отсутствие дубликатов у newChange
        /// </summary>
        private void checkUniqueScheduleChanges(wScheduleChanxNode newChanges)
        {
            // http://blogs.msdn.com/b/efdesign/archive/2011/03/09/unique-constraints-in-the-entity-framework.aspx
            // говорят, низзя делать unique non-primary keys.
            // приходится заботиться о дубликатах так вот(

            var dublicate = getDublicateOfScheduleChanx(newChanges);
            if (dublicate != null)
                throw new DataException("Dublicate. ScheduleChange with the DateOfChange and ScheduleID already exists.");
        }

        /// <summary>
        /// Возвращает дубликат для newChanges. Дубликатом считается запись в БД, для того же транспорта(schedule_id)
        /// с той же датой изенения(DateOfChange)
        /// </summary>
        /// <param name="newChanges"></param>
        private ScheduleChanx getDublicateOfScheduleChanx(wScheduleChanxNode newChanges)
        {
            var change = (from c in dataModel.ScheduleChanges
                          where c.DateOfChange == newChanges.DateOfChange &&
                                c.ScheduleId == newChanges.ScheduleId && 
                                newChanges.ScheduleId != null && // для Добавленных поездов уникальность учитывать не надо
                                c.Id != newChanges.Id            // сам дубликат себя же?
                          select c).SingleOrDefault();
            return change;
        }

        /// <summary>
        /// Проверка входных данных при добавлении/изменении Расписания. Проверяется только логичность
        /// с точки зрения бизнес-логики.
        /// Корректность данных(out of range для Времени отправления и пр.) проверяется самой СУБД.
        /// </summary>
        private void checkNewSchedule(wScheduleNode scheduleNode)
        {
            // нет смысла добавлять расписание без дней недели
            if (scheduleNode.DaysOfWeek.Count == 0)
                throw new ArgumentException("No elements in DaysOfWeek");
        }
        #endregion
    }
}
